#!/usr/bin/env python3
# -*- coding:utf-8 -*-

import os
import os.path
import sys
import subprocess
from typing import Iterable

from ctools.datahelper.functree import *

import logging
from ctools.logext import *

_fpath = os.path.dirname(os.path.realpath(__file__))
_rlog, _plog, _mlog, _tlog = get_loggers(__file__, __name__, propagate=False)

'''
0或s: 查找这个(指name参数, 下同)C符号
1或g: 查找这个定义
2或d: 查找被这个函数调用的函数
3或c: 查找调用该函数的函数
4或t: 查找这个文本字符串。
6或e: 查找这个egrep的pattern
7或f: 查找这个文件
8或i: 查找#include了这个文件的所有文件
'''

def _have_subnode(_list:list, itemname) -> bool:
    return itemname in [x.name for x in _list]

class CScopeTree(TreeProvider):
    _keywords = {'defined'}

    def __init__(self, dbpath, srcpath=None) -> None:
        super().__init__()
        self.dbpath = os.path.realpath(dbpath)
        if srcpath is None:
            srcpath = os.path.dirname(self.dbpath)
        self.srcpath = os.path.dirname(os.path.realpath(srcpath))
        try:
            cmd = ['which', 'cscope']
            self._cscope_exe = subprocess.check_output(cmd, universal_newlines=True).strip()
            _mlog.debug(f'cscope_exe: {self._cscope_exe}')
        except Exception as e:
            raise e

    def _do_cscope_cmd(self, args):
        try:
            cmd = [self._cscope_exe, '-f', f'{self.dbpath}']
            if isinstance(args, Iterable):
                cmd.extend(args)
            elif isinstance(args, str):
                cmd.append(args)
            else:
                _mlog.exception(f'unknown args type: {type(args)}')

            _mlog.debug('cmd: {}'.format(' '.join(cmd)))
            ret = subprocess.check_output(cmd, universal_newlines=True)
            return ret
        except Exception as e:
            _mlog.error(f'call cscope failed: {e}')
            return ''

    def get_definition(self, funcname, opts=['-d', '-c', '-L', '-1']):
        cmd = opts + [funcname,]
        ret = self._do_cscope_cmd(cmd)
        ret = ret.strip()
        lines = ret.split('\n')
        _mlog.debug(lines)

        define_list = []
        for line in lines:
            line = line.strip()
            if len(line) == 0:
                continue
            line = line.split()
            try:
                filename = line[0]
                lineno = int(line[2])
                declaration = ' '.join(line[3:])
                tmp = ('func', funcname, filename, lineno, declaration)
                define_list.append(tmp)
            except Exception as e:
                _mlog.error(f"line: {line}")
                _mlog.exception(f'get_definition failed: {e}')
        return define_list

    def get_callee(self, funcname, opts=['-d', '-c', '-L', '-2'], unique=False):
        cmd = opts + [funcname,]
        ret = self._do_cscope_cmd(cmd)
        ret = ret.strip()
        lines = ret.split('\n')
        _mlog.debug(lines)

        callee_list = []
        for line in lines:
            line = line.strip()
            if len(line) == 0:
                continue
            line = line.split()
            try:
                filename = line[0]
                callee_short = line[1]
                if unique and _have_subnode(callee_list, callee_short):
                    continue
                lineno = int(line[2])
                callee_whole = ' '.join(line[3:])
                if callee_short in self._keywords:
                    continue
                tmp = SNode(callee_short, '<')
                tmp.data = (filename, lineno, callee_short, callee_whole)
                callee_list.append(tmp)
            except Exception as e:
                _mlog.error(f"line: {line}")
                _mlog.exception(f'get_callee failed: {e}')
        return callee_list

    def get_caller(self, funcname, opts=['-d', '-c', '-L', '-3'], unique=False):
        cmd = opts + [funcname,]
        ret = self._do_cscope_cmd(cmd)
        ret = ret.strip()
        lines = ret.split('\n')
        _mlog.debug(lines)

        caller_list = []
        for line in lines:
            line = line.strip()
            if len(line) == 0:
                continue
            line = line.split()
            try:
                filename = line[0]
                caller = line[1]
                if unique and _have_subnode(caller_list, caller):
                    continue
                lineno = int(line[2])
                call_expr = ' '.join(line[3:])
                tmp = SNode(caller, '<')
                tmp.data = (filename, lineno, caller, call_expr)
                caller_list.append(tmp)
            except Exception as e:
                _mlog.error(f"line: {line}")
                _mlog.exception(f'get_caller failed: {e}')
        return caller_list

    def _get_items(self, pattern, direction, unique=False) -> list|tuple:
        if '>' in direction:
            return self.get_callee(pattern, unique=unique)
        else:
            return self.get_caller(pattern, unique=unique)
